package il.ac.tau.cg.ex3.parser;

import il.ac.tau.cg.ex3.entities.scene.Scene;
import il.ac.tau.cg.ex3.parser.parametershander.CameraParametersHandler;
import il.ac.tau.cg.ex3.parser.parametershander.EntityParameters;
import il.ac.tau.cg.ex3.parser.parametershander.EntityParametersHandler;
import il.ac.tau.cg.ex3.parser.parametershander.SceneParameterHandler;
import il.ac.tau.cg.ex3.parser.parametershander.lights.AreaLightParametersHandler;
import il.ac.tau.cg.ex3.parser.parametershander.lights.DirectionalLightParametersHandler;
import il.ac.tau.cg.ex3.parser.parametershander.lights.PointLightParametersHandler;
import il.ac.tau.cg.ex3.parser.parametershander.surfaces.BoxParameterHandler;
import il.ac.tau.cg.ex3.parser.parametershander.surfaces.CylinderParameterHandler;
import il.ac.tau.cg.ex3.parser.parametershander.surfaces.DiscParameterHandler;
import il.ac.tau.cg.ex3.parser.parametershander.surfaces.RectangleParameterHandler;
import il.ac.tau.cg.ex3.parser.parametershander.surfaces.SphereParameterHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

public class Parser
{
	private EntityParametersHandler handler;
	private Scene scene;
	
	public Parser()
	{
		this.scene = null;
	}

	public Parser(Scene scene)
	{
		this.scene = scene;
	}
	
	public final void parse(Reader in) throws IOException, ParseException
	{
		BufferedReader r = new BufferedReader(in);
		String line, curobj = null;
		int lineNum = 0;
		
		startFile();

		while ((line = r.readLine()) != null)
		{
			line = line.trim();
			++lineNum;

			if (line.isEmpty() || (line.charAt(0) == '#'))
			{ // comment
				continue;
			}
			else if (line.charAt(line.length() - 1) == ':')
			{ // new object;
				if (curobj != null)
					commit();
				curobj = line.substring(0, line.length() - 1).trim().toLowerCase();
				if (!addObject(curobj))
					reportError(String.format("Did not recognize object: %s (line %d)", curobj, lineNum));
			}
			else
			{
				int eqIndex = line.indexOf('=');
				if (eqIndex == -1)
				{
					reportError(String.format("Syntax error line %d: %s", lineNum, line));
					continue;
				}
				String name = line.substring(0, eqIndex).trim().toLowerCase();
				String[] args = line.substring(eqIndex + 1).trim().toLowerCase().split("\\s+");

				if (curobj == null)
				{
					reportError(String.format("parameter with no object %s (line %d)", name, lineNum));
					continue;
				}

				if (!setParameter(name, args))
					reportError(String.format("Did not recognize parameter: %s of object %s (line %d)", name, curobj,
							lineNum));
			}
		}
		
		if (curobj != null)
			commit();

		endFile();
	}

	// utility assetion.
	// use this for parameter validity checks.
	public static void pAssert(Boolean v, String msg) throws ParseException
	{
		if (!v)
			throw new ParseException(msg);
	}

	/*** override these methods in your implementation ***/

	public void startFile()
	{
		if (scene == null)
		{
			scene = new Scene();
		}
	}

	public void endFile() throws ParseException
	{
	}

	public Scene getScene()
	{
		return scene;
	}
	
	// start a new object definition
	// return true if recognized
	public boolean addObject(String name) throws ParseException
	{
		if (name.equals(EntityParameters.SCENE))
		{
			handler = new SceneParameterHandler();
		}
		else if (name.equals(EntityParameters.CAMERA))
		{
			handler = new CameraParametersHandler();
		}
		else if (name.equals(EntityParameters.LIGHT_DIRECTED))
		{
			handler = new DirectionalLightParametersHandler();
		}
		else if (name.equals(EntityParameters.LIGHT_POINT))
		{
			handler = new PointLightParametersHandler();
		}
		else if (name.equals(EntityParameters.LIGHT_AREA))
		{
			handler = new AreaLightParametersHandler();
		}
		else if (name.equals(EntityParameters.RECTANGLE))
		{
			handler = new RectangleParameterHandler();
		}
		else if (name.equals(EntityParameters.SPHERE))
		{
			handler = new SphereParameterHandler();
		}
		else if (name.equals(EntityParameters.DISC))
		{
			handler = new DiscParameterHandler();
		}
		else if (name.equals(EntityParameters.BOX))
		{
			handler = new BoxParameterHandler();
		}
		else if (name.equals(EntityParameters.CYLINDER))
		{
			handler = new CylinderParameterHandler();
		}
		else
		{
			return false;
		}
		
		return true;
	}

	// set a specific parameter for the current object
	// return true if recognized
	public boolean setParameter(String name, String[] args) throws ParseException
	{
		return handler.addParam(name, args);
	}

	// finish the parsing of the current object
	// here is the place to perform any validations over the parameters and
	// final initializations.
	public void commit() throws ParseException
	{
		handler.commit(scene);
	}

	public void reportError(String err)
	{
		System.out.println("ERROR: " + err);
	}
	
	public static class ParseException extends Exception
	{
		private static final long serialVersionUID = 1L;

		public ParseException(String msg)
		{
			super(msg);
		}
	}
}
